Completed
Push — master ( 52af4d...0f6b70 )
by Rain
01:41
created

Knoin.js ➔ screen   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
c 2
b 0
f 0
nc 4
nop 1
dl 0
loc 4
rs 10
1
2
import _ from '_';
3
import $ from '$';
4
import ko from 'ko';
5
import hasher from 'hasher';
6
import crossroads from 'crossroads';
7
8
import {Magics} from 'Common/Enums';
9
import {runHook} from 'Common/Plugins';
10
import {$html, VIEW_MODELS, popupVisibilityNames} from 'Common/Globals';
11
12
import {
13
	isArray, isUnd, pString, log, isFunc,
14
	createCommandLegacy, delegateRun, isNonEmptyArray
15
} from 'Common/Utils';
16
17
let
18
	currentScreen = null,
19
	defaultScreenName = '';
20
21
const SCREENS = {};
22
23
export const ViewType = {
24
	Popup: 'Popups',
25
	Left: 'Left',
26
	Right: 'Right',
27
	Center: 'Center'
28
};
29
30
/**
31
 * @returns {void}
32
 */
33
export function hideLoading()
34
{
35
	$('#rl-content').addClass('rl-content-show');
36
	$('#rl-loading').hide().remove();
37
}
38
39
/**
40
 * @param {Function} fExecute
41
 * @param {(Function|boolean|null)=} fCanExecute = true
42
 * @returns {Function}
43
 */
44
export function createCommand(fExecute, fCanExecute = true)
45
{
46
	return createCommandLegacy(null, fExecute, fCanExecute);
47
}
48
49
/**
50
 * @param {Function} SettingsViewModelClass
51
 * @param {string} template
52
 * @param {string} labelName
53
 * @param {string} route
54
 * @param {boolean=} isDefault = false
55
 * @returns {void}
56
 */
57
export function addSettingsViewModel(SettingsViewModelClass, template, labelName, route, isDefault = false)
58
{
59
	SettingsViewModelClass.__rlSettingsData = {
60
		Label: labelName,
61
		Template: template,
62
		Route: route,
63
		IsDefault: !!isDefault
64
	};
65
66
	VIEW_MODELS.settings.push(SettingsViewModelClass);
67
}
68
69
/**
70
 * @param {Function} SettingsViewModelClass
71
 * @returns {void}
72
 */
73
export function removeSettingsViewModel(SettingsViewModelClass)
74
{
75
	VIEW_MODELS['settings-removed'].push(SettingsViewModelClass);
76
}
77
78
/**
79
 * @param {Function} SettingsViewModelClass
80
 * @returns {void}
81
 */
82
export function disableSettingsViewModel(SettingsViewModelClass)
83
{
84
	VIEW_MODELS['settings-disabled'].push(SettingsViewModelClass);
85
}
86
87
/**
88
 * @returns {void}
89
 */
90
export function routeOff()
91
{
92
	hasher.changed.active = false;
93
}
94
95
/**
96
 * @returns {void}
97
 */
98
export function routeOn()
99
{
100
	hasher.changed.active = true;
101
}
102
103
/**
104
 * @param {string} screenName
105
 * @returns {?Object}
106
 */
107
export function screen(screenName)
108
{
109
	return ('' !== screenName && !isUnd(SCREENS[screenName])) ? SCREENS[screenName] : null;
110
}
111
112
/**
113
 * @param {Function} ViewModelClassToShow
0 ignored issues
show
Documentation introduced by
The parameter ViewModelClassToShow does not exist. Did you maybe forget to remove this comment?
Loading history...
114
 * @returns {Function|null}
115
 */
116
export function getScreenPopup(PopuViewModelClass)
117
{
118
	let result = null;
119
	if (PopuViewModelClass)
120
	{
121
		result = PopuViewModelClass;
122
		if (PopuViewModelClass.default)
123
		{
124
			result = PopuViewModelClass.default;
125
		}
126
	}
127
128
	return result;
129
}
130
131
/**
132
 * @param {Function} ViewModelClassToHide
133
 * @returns {void}
134
 */
135
export function hideScreenPopup(ViewModelClassToHide)
136
{
137
	const ModalView = getScreenPopup(ViewModelClassToHide);
138
	if (ModalView && ModalView.__vm && ModalView.__dom)
139
	{
140
		ModalView.__vm.modalVisibility(false);
141
	}
142
}
143
144
/**
145
 * @param {string} hookName
146
 * @param {Function} ViewModelClass
147
 * @param {mixed=} params = null
148
 */
149
export function vmRunHook(hookName, ViewModelClass, params = null)
150
{
151
	_.each(ViewModelClass.__names, (name) => {
152
		runHook(hookName, [name, ViewModelClass.__vm, params]);
153
	});
154
}
155
156
/**
157
 * @param {Function} ViewModelClass
158
 * @param {Object=} vmScreen
159
 * @returns {*}
160
 */
161
export function buildViewModel(ViewModelClass, vmScreen)
162
{
163
	if (ViewModelClass && !ViewModelClass.__builded)
164
	{
165
		let vmDom = null;
0 ignored issues
show
Unused Code introduced by
The assignment to vmDom seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
166
		const
167
			vm = new ViewModelClass(vmScreen),
168
			position = ViewModelClass.__type || '',
169
			vmPlace = position ? $('#rl-content #rl-' + position.toLowerCase()) : null;
170
171
		ViewModelClass.__builded = true;
172
		ViewModelClass.__vm = vm;
173
174
		vm.onShowTrigger = ko.observable(false);
175
		vm.onHideTrigger = ko.observable(false);
176
177
		vm.viewModelName = ViewModelClass.__name;
178
		vm.viewModelNames = ViewModelClass.__names;
179
		vm.viewModelTemplateID = ViewModelClass.__templateID;
180
		vm.viewModelPosition = ViewModelClass.__type;
181
182
		if (vmPlace && 1 === vmPlace.length)
183
		{
184
			vmDom = $('<div></div>').addClass('rl-view-model').addClass('RL-' + vm.viewModelTemplateID).hide();
185
			vmDom.appendTo(vmPlace);
186
187
			vm.viewModelDom = vmDom;
188
			ViewModelClass.__dom = vmDom;
189
190
			if (ViewType.Popup === position)
191
			{
192
				vm.cancelCommand = vm.closeCommand = createCommand(() => {
193
					hideScreenPopup(ViewModelClass);
194
				});
195
196
				vm.modalVisibility.subscribe((value) => {
197
					if (value)
198
					{
199
						vm.viewModelDom.show();
200
						vm.storeAndSetKeyScope();
201
202
						popupVisibilityNames.push(vm.viewModelName);
203
						vm.viewModelDom.css('z-index', 3000 + popupVisibilityNames().length + 10);
204
205
						if (vm.onShowTrigger)
206
						{
207
							vm.onShowTrigger(!vm.onShowTrigger());
208
						}
209
210
						delegateRun(vm, 'onShowWithDelay', [], 500);
211
					}
212
					else
213
					{
214
						delegateRun(vm, 'onHide');
215
						delegateRun(vm, 'onHideWithDelay', [], 500);
216
217
						if (vm.onHideTrigger)
218
						{
219
							vm.onHideTrigger(!vm.onHideTrigger());
220
						}
221
222
						vm.restoreKeyScope();
223
224
						vmRunHook('view-model-on-hide', ViewModelClass);
225
226
						popupVisibilityNames.remove(vm.viewModelName);
227
						vm.viewModelDom.css('z-index', 2000);
228
229
						_.delay(() => vm.viewModelDom.hide(), 300);
230
					}
231
				});
232
			}
233
234
			vmRunHook('view-model-pre-build', ViewModelClass, vmDom);
235
236
			ko.applyBindingAccessorsToNode(vmDom[0], {
237
				translatorInit: true,
238
				template: () => ({name: vm.viewModelTemplateID})
239
			}, vm);
240
241
			delegateRun(vm, 'onBuild', [vmDom]);
242
			if (vm && ViewType.Popup === position)
243
			{
244
				vm.registerPopupKeyDown();
245
			}
246
247
			vmRunHook('view-model-post-build', ViewModelClass, vmDom);
248
		}
249
		else
250
		{
251
			log('Cannot find view model position: ' + position);
252
		}
253
	}
254
255
	return ViewModelClass ? ViewModelClass.__vm : null;
256
}
257
258
/**
259
 * @param {Function} ViewModelClassToShow
260
 * @param {Array=} params
261
 * @returns {void}
262
 */
263
export function showScreenPopup(ViewModelClassToShow, params = [])
264
{
265
	const ModalView = getScreenPopup(ViewModelClassToShow);
266
	if (ModalView)
267
	{
268
		buildViewModel(ModalView);
269
270
		if (ModalView.__vm && ModalView.__dom)
271
		{
272
			delegateRun(ModalView.__vm, 'onBeforeShow', params || []);
273
274
			ModalView.__vm.modalVisibility(true);
275
276
			delegateRun(ModalView.__vm, 'onShow', params || []);
277
278
			vmRunHook('view-model-on-show', ModalView, params || []);
279
		}
280
	}
281
}
282
283
/**
284
 * @param {Function} ViewModelClassToShow
285
 * @returns {void}
286
 */
287
export function warmUpScreenPopup(ViewModelClassToShow)
288
{
289
	const ModalView = getScreenPopup(ViewModelClassToShow);
290
	if (ModalView)
291
	{
292
		buildViewModel(ModalView);
293
294
		if (ModalView.__vm && ModalView.__dom)
295
		{
296
			delegateRun(ModalView.__vm, 'onWarmUp');
297
		}
298
	}
299
}
300
301
/**
302
 * @param {Function} ViewModelClassToShow
303
 * @returns {boolean}
304
 */
305
export function isPopupVisible(ViewModelClassToShow)
306
{
307
	const ModalView = getScreenPopup(ViewModelClassToShow);
308
	return ModalView && ModalView.__vm ? ModalView.__vm.modalVisibility() : false;
309
}
310
311
/**
312
 * @param {string} screenName
313
 * @param {string} subPart
314
 * @returns {void}
315
 */
316
export function screenOnRoute(screenName, subPart)
317
{
318
	let
319
		vmScreen = null,
320
		isSameScreen = false,
321
		cross = null;
322
323
	if ('' === pString(screenName))
324
	{
325
		screenName = defaultScreenName;
326
	}
327
328
	if ('' !== screenName)
329
	{
330
		vmScreen = screen(screenName);
331
		if (!vmScreen)
332
		{
333
			vmScreen = screen(defaultScreenName);
334
			if (vmScreen)
335
			{
336
				subPart = screenName + '/' + subPart;
337
				screenName = defaultScreenName;
338
			}
339
		}
340
341
		if (vmScreen && vmScreen.__started)
342
		{
343
			isSameScreen = currentScreen && vmScreen === currentScreen;
344
345
			if (!vmScreen.__builded)
346
			{
347
				vmScreen.__builded = true;
348
349
				if (isNonEmptyArray(vmScreen.viewModels()))
350
				{
351
					_.each(vmScreen.viewModels(), (ViewModelClass) => {
352
						buildViewModel(ViewModelClass, vmScreen);
353
					});
354
				}
355
356
				delegateRun(vmScreen, 'onBuild');
357
			}
358
359
			_.defer(() => {
360
				// hide screen
361
				if (currentScreen && !isSameScreen)
362
				{
363
					delegateRun(currentScreen, 'onHide');
364
					delegateRun(currentScreen, 'onHideWithDelay', [], 500);
365
366
					if (currentScreen.onHideTrigger)
367
					{
368
						currentScreen.onHideTrigger(!currentScreen.onHideTrigger());
369
					}
370
371
					if (isNonEmptyArray(currentScreen.viewModels()))
372
					{
373
						_.each(currentScreen.viewModels(), (ViewModelClass) => {
374
							if (ViewModelClass.__vm && ViewModelClass.__dom && ViewType.Popup !== ViewModelClass.__vm.viewModelPosition)
375
							{
376
								ViewModelClass.__dom.hide();
377
								ViewModelClass.__vm.viewModelVisibility(false);
378
379
								delegateRun(ViewModelClass.__vm, 'onHide');
380
								delegateRun(ViewModelClass.__vm, 'onHideWithDelay', [], 500);
381
382
								if (ViewModelClass.__vm.onHideTrigger)
383
								{
384
									ViewModelClass.__vm.onHideTrigger(!ViewModelClass.__vm.onHideTrigger());
385
								}
386
							}
387
						});
388
					}
389
				}
390
				// --
391
392
				currentScreen = vmScreen;
393
394
				// show screen
395
				if (currentScreen && !isSameScreen)
396
				{
397
					delegateRun(currentScreen, 'onShow');
398
					if (currentScreen.onShowTrigger)
399
					{
400
						currentScreen.onShowTrigger(!currentScreen.onShowTrigger());
401
					}
402
403
					runHook('screen-on-show', [currentScreen.screenName(), currentScreen]);
404
405
					if (isNonEmptyArray(currentScreen.viewModels()))
406
					{
407
						_.each(currentScreen.viewModels(), (ViewModelClass) => {
408
409
							if (ViewModelClass.__vm && ViewModelClass.__dom && ViewType.Popup !== ViewModelClass.__vm.viewModelPosition)
410
							{
411
								delegateRun(ViewModelClass.__vm, 'onBeforeShow');
412
413
								ViewModelClass.__dom.show();
414
								ViewModelClass.__vm.viewModelVisibility(true);
415
416
								delegateRun(ViewModelClass.__vm, 'onShow');
417
								if (ViewModelClass.__vm.onShowTrigger)
418
								{
419
									ViewModelClass.__vm.onShowTrigger(!ViewModelClass.__vm.onShowTrigger());
420
								}
421
422
								delegateRun(ViewModelClass.__vm, 'onShowWithDelay', [], 200);
423
								vmRunHook('view-model-on-show', ViewModelClass);
424
							}
425
426
						});
427
					}
428
				}
429
				// --
430
431
				cross = vmScreen && vmScreen.__cross ? vmScreen.__cross() : null;
432
				if (cross)
433
				{
434
					cross.parse(subPart);
435
				}
436
			});
437
		}
438
	}
439
}
440
441
/**
442
 * @param {Array} screensClasses
443
 * @returns {void}
444
 */
445
export function startScreens(screensClasses)
446
{
447
	_.each(screensClasses, (CScreen) => {
448
		if (CScreen)
449
		{
450
			const
451
				vmScreen = new CScreen(),
452
				screenName = vmScreen ? vmScreen.screenName() : '';
453
454
			if (vmScreen && '' !== screenName)
455
			{
456
				if ('' === defaultScreenName)
457
				{
458
					defaultScreenName = screenName;
459
				}
460
461
				SCREENS[screenName] = vmScreen;
462
			}
463
		}
464
	});
465
466
	_.each(SCREENS, (vmScreen) => {
467
		if (vmScreen && !vmScreen.__started && vmScreen.__start)
468
		{
469
			vmScreen.__started = true;
470
			vmScreen.__start();
471
472
			runHook('screen-pre-start', [vmScreen.screenName(), vmScreen]);
473
			delegateRun(vmScreen, 'onStart');
474
			runHook('screen-post-start', [vmScreen.screenName(), vmScreen]);
475
		}
476
	});
477
478
	const cross = crossroads.create();
479
	cross.addRoute(/^([a-zA-Z0-9\-]*)\/?(.*)$/, screenOnRoute);
480
481
	hasher.initialized.add(cross.parse, cross);
482
	hasher.changed.add(cross.parse, cross);
483
	hasher.init();
484
485
	_.delay(() => $html.removeClass('rl-started-trigger').addClass('rl-started'), 100);
486
	_.delay(() => $html.addClass('rl-started-delay'), 200);
487
}
488
489
/**
490
 * @param {string} sHash
0 ignored issues
show
Documentation introduced by
The parameter sHash does not exist. Did you maybe forget to remove this comment?
Loading history...
491
 * @param {boolean=} silence = false
492
 * @param {boolean=} replace = false
493
 * @returns {void}
494
 */
495
export function setHash(hash, silence = false, replace = false)
496
{
497
	hash = '#' === hash.substr(0, 1) ? hash.substr(1) : hash;
498
	hash = '/' === hash.substr(0, 1) ? hash.substr(1) : hash;
499
500
	const cmd = replace ? 'replaceHash' : 'setHash';
501
502
	if (silence)
503
	{
504
		hasher.changed.active = false;
505
		hasher[cmd](hash);
506
		hasher.changed.active = true;
507
	}
508
	else
509
	{
510
		hasher.changed.active = true;
511
		hasher[cmd](hash);
512
		hasher.setHash(hash);
513
	}
514
}
515
516
/**
517
 * @param {Object} params
0 ignored issues
show
Documentation introduced by
The parameter params does not exist. Did you maybe forget to remove this comment?
Loading history...
518
 * @returns {Function}
519
 */
520
function viewDecorator({name, type, templateID})
521
{
522
	return (target) => {
523
		if (target)
524
		{
525
			if (name)
526
			{
527
				if (isArray(name))
528
				{
529
					target.__names = name;
530
				}
531
				else
532
				{
533
					target.__names = [name];
534
				}
535
536
				target.__name = target.__names[0];
537
			}
538
539
			if (type)
540
			{
541
				target.__type = type;
542
			}
543
544
			if (templateID)
545
			{
546
				target.__templateID = templateID;
547
			}
548
		}
549
	};
550
}
551
552
/**
553
 * @param {Object} params
0 ignored issues
show
Documentation introduced by
The parameter params does not exist. Did you maybe forget to remove this comment?
Loading history...
554
 * @returns {Function}
555
 */
556
function popupDecorator({name, templateID})
557
{
558
	return viewDecorator({name, type: ViewType.Popup, templateID});
559
}
560
561
/**
562
 * @param {Function} canExecute
563
 * @returns {Function}
564
 */
565
function commandDecorator(canExecute = true)
566
{
567
	return (target, key, descriptor) => {
568
569
		if (!key || !key.match(/Command$/))
570
		{
571
			throw new Error(`name "${key}" should end with Command suffix`);
572
		}
573
574
		const
575
			value = descriptor.value || descriptor.initializer(),
576
			normCanExecute = isFunc(canExecute) ? canExecute : () => !!canExecute;
577
578
		descriptor.value = function(...args) {
579
			if (normCanExecute.call(this, this))
580
			{
581
				value.apply(this, args);
582
			}
583
584
			return false;
585
		};
586
587
		descriptor.value.__realCanExecute = normCanExecute;
588
		descriptor.value.isCommand = true;
589
590
		return descriptor;
591
	};
592
}
593
594
/**
595
 * @param {miced} $items
596
 * @returns {Function}
597
 */
598
function settingsMenuKeysHendler($items)
599
{
600
	return _.throttle((event, handler) => {
601
602
		const up = handler && 'up' === handler.shortcut;
603
604
		if (event && $items.length)
605
		{
606
			let index = $items.index($items.filter('.selected'));
607
			if (up && 0 < index)
608
			{
609
				index -= 1;
610
			}
611
			else if (!up && index < $items.length - 1)
612
			{
613
				index += 1;
614
			}
615
616
			const resultHash = $items.eq(index).attr('href');
617
			if (resultHash)
618
			{
619
				setHash(resultHash, false, true);
620
			}
621
		}
622
623
	}, Magics.Time200ms);
624
}
625
626
export {
627
	commandDecorator, commandDecorator as command,
628
	viewDecorator, viewDecorator as view, viewDecorator as viewModel,
629
	popupDecorator, popupDecorator as popup,
630
	settingsMenuKeysHendler
631
};
632